Skip to content

General: Add opt-in <search> element support to the core search form#11913

Open
adamsilverstein wants to merge 2 commits into
WordPress:trunkfrom
adamsilverstein:feature/html-search-element
Open

General: Add opt-in <search> element support to the core search form#11913
adamsilverstein wants to merge 2 commits into
WordPress:trunkfrom
adamsilverstein:feature/html-search-element

Conversation

@adamsilverstein
Copy link
Copy Markdown
Member

@adamsilverstein adamsilverstein commented May 20, 2026

Summary

Adds opt-in support for the HTML <search> landmark element in get_search_form() (the function behind core's default search markup and the search widget).

Trac ticket: https://core.trac.wordpress.org/ticket/65288

Why opt-in?

The <search> element is in Baseline (Chrome 118+, Firefox 118+, Safari 17+, Edge 118+) and carries an implicit ARIA role of search. But adopting it unconditionally is not back-compatible:

  • Dropping role="search" breaks form[role="search"] CSS selectors (used by themes in the wild).
  • Wrapping the form in <search> breaks direct-child selectors like .search-container > form and reparents the form for flex/grid layouts - regardless of whether the role is kept.
  • Keeping role="search" alongside <search> produces nested search landmarks, which NVDA announces twice ("search landmark search landmark").

There is no way to add the native landmark without a new wrapper node, so the safe path is to make it opt-in now and plan to change the default later.

What this adds

1. A search-element theme support feature. A theme that has audited its CSS opts in once:

add_theme_support( 'search-element' );

2. A wrap_in_search argument on get_search_form(), defaulting to current_theme_supports( 'search-element' ):

get_search_form( array( 'wrap_in_search' => true ) );

The existing search_form_args filter can enable it site-wide; a per-call value overrides the theme default.

When enabled (html5 format only): the <form> is wrapped in <search>, role="search" is dropped from the form to avoid a nested landmark, and any aria_label names the <search> element.

<!-- default (unchanged) -->
<form role="search" class="search-form" ...></form>

<!-- opted in -->
<search><form method="get" class="search-form" ...></form></search>

Scope and backward compatibility

  • Default output is unchanged (byte-for-byte). Existing sites are unaffected unless they opt in.
  • The xhtml fallback is unchanged (<search> does not exist in XHTML 1.x).
  • The bundled classic themes honor the flag (Twenty Sixteen, Twenty Seventeen, Twenty Twenty, Twenty Twenty-One): their default searchform.php output is unchanged (byte-for-byte), and they emit the <search> wrapper only when a site opts in. In Twenty Twenty / Twenty Twenty-One the aria_label moves onto <search> when wrapping.
  • The core/search block is handled in Gutenberg: Search block: Render in the semantic <search> element gutenberg#78485

Migration plan

  • 7.1: ship opt-in support + a developer note documenting the element, the opt-in mechanisms, and the CSS/accessibility considerations.
  • Later: once the ecosystem has adapted, consider making the wrapper the default (e.g. for block themes first), tracked as a follow-up.

Test plan

New tests in tests/phpunit/tests/general/getSearchForm.php cover:

  • Default html5 markup keeps role="search" and is not wrapped.
  • wrap_in_search wraps the form in <search> and drops role="search".
  • aria_label lands on <search> (not the inner form) when wrapping; on the form by default.
  • The xhtml format is unaffected by wrap_in_search.
  • The search_form_args filter can enable wrapping site-wide.
  • add_theme_support( 'search-element' ) enables wrapping by default; an explicit wrap_in_search => false overrides it.

9 tests, 22 assertions, passing.

The four bundled searchform.php templates were verified by rendering each in both states: default output is byte-identical to trunk, and the opt-in output wraps the form in <search>, drops role="search", and (Twenty Twenty / Twenty Twenty-One) places aria_label on <search>.

References

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props adamsilverstein, westonruter, joedolson.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@westonruter
Copy link
Copy Markdown
Member

I'd be concerned about breaking sites using CSS selectors like header > form[role="search"].

Themes using the form[role="search"] selector: https://veloria.dev/search/9e01303f-ef26-4607-9ea5-39ea2097710f

@adamsilverstein
Copy link
Copy Markdown
Member Author

I'd be concerned about breaking sites using CSS selectors like header > form[role="search"].

Themes using the form[role="search"] selector: https://veloria.dev/search/9e01303f-ef26-4607-9ea5-39ea2097710f

Good point. I wonder if we can just leave the role on form and still add the search wrapper?

@westonruter
Copy link
Copy Markdown
Member

I think that would be safer, yes. I couldn't find any descendant selectors being used like * > form[role="search"] (although perhaps there is somewhere not searchable).

But then you'd have a search role wrapping another element with a search role, right?

@adamsilverstein
Copy link
Copy Markdown
Member Author

I think that would be safer, yes. I couldn't find any descendant selectors being used like * > form[role="search"] (although perhaps there is somewhere not searchable).

But then you'd have a search role wrapping another element with a search role, right?

Right, I don't know if thats better or worse, I'm curious to hear what @joedolson thinks.

@joedolson
Copy link
Copy Markdown
Contributor

I'd definitely be worried about this impacting theme CSS - there's also the potential use of direct descendant selectors that could cause problems, e.g .search-container > form.

The default search form should possibly also come with an aria-label to name it as the general site search. While this isn't required in the case that there's only one search form (also true if there are multiple search forms, but they do the same thing), it would be required if anybody included a separate search form, e.g. <search aria-label="Search Products">.

I tested the different behaviors having nested roles, and unfortunately that does result in the landmark being announced twice. Codepen: https://codepen.io/joedolson/full/RNogReV

In NVDA, the first landmark is announced as "Named Search Region search landmark search landmark". Without the aria-label, it's just "search landmark search landmark" - but either way, they both get announced. This makes sense, since the announcement happens on focus to the field, and it's inside two search landmarks. So from the accessibility standpoint, having both is definitely worse. Not a barrier, but a constant irritation to users, for sure.

@adamsilverstein
Copy link
Copy Markdown
Member Author

adamsilverstein commented May 21, 2026

My best idea for a path forward is a new option in get_search_form args to make it opt in to get the search wrapper (removing the current aria role); leaving core themes unchanged. Then perhaps in the dev note we can mention that at some point this will become the fault and plan to swap that at a later time.

The HTML `<search>` element is in Baseline and carries an implicit ARIA
landmark role of `search`, expressing natively what `get_search_form()`
currently does with a manual `role="search"` attribute. Adopting it
unconditionally is not back-compatible: wrapping the `<form>` in a new
element breaks direct-child CSS selectors (e.g. `.search-container > form`)
and reparents the form for flex/grid layouts, dropping `role="search"`
breaks `form[role="search"]` selectors, and keeping the role alongside the
wrapper produces nested, double-announced search landmarks.

Make the wrapper opt in instead:

- Add a `search-element` theme support feature. Themes that have audited
  their CSS declare `add_theme_support( 'search-element' )` to enable it.
- Add a `wrap_in_search` argument to `get_search_form()`, defaulting to
  `current_theme_supports( 'search-element' )`. The `search_form_args`
  filter can toggle it site-wide, and a per-call value overrides both.

When enabled (html5 format only), the form is wrapped in `<search>`,
`role="search"` is dropped to avoid a nested landmark, and any `aria_label`
names the `<search>` element. The default output, the xhtml fallback, and
the bundled classic themes are left unchanged.

Add unit tests covering the default and wrapped markup, aria-label
placement, the xhtml fallback, the filter, and the theme-support default.

See #65288.
@adamsilverstein adamsilverstein force-pushed the feature/html-search-element branch from 657f809 to 540ac7a Compare May 21, 2026 23:30
@adamsilverstein adamsilverstein changed the title General: Use the semantic <search> element in core search markup General: Add opt-in <search> element support to the core search form May 21, 2026
@adamsilverstein
Copy link
Copy Markdown
Member Author

Following up on my note above about making this opt-in: I've reworked the PR along those lines, and added theme support as a second (theme-level) way to opt in.

What changed

The <search> wrapper is now opt-in and off by default, so no existing markup or CSS is affected unless a theme/site asks for it:

  • add_theme_support( 'search-element' ) - a theme that has audited its CSS enables it once.
  • get_search_form( array( 'wrap_in_search' => true ) ) - per-call, defaulting to the theme-support value; the search_form_args filter can flip it site-wide.

When enabled, the form is wrapped in <search> and role="search" is dropped from the form, so we get a single, native landmark with no duplication. When not enabled, the output is byte-for-byte identical to today.

How this addresses the feedback

  • @westonruter's form[role="search"] concern: the default output is unchanged, so those selectors keep working. Sites only take the new markup when they opt in (having presumably audited their CSS).
  • @joedolson's nested-landmark finding: opting in is all-or-nothing, so we never keep role="search" alongside <search> and there's no double-announced landmark. Your point about naming the landmark is handled too: a custom aria_label now lands on <search>.
  • The direct-child (.search-container > form) and flex/grid reparenting risks are real for any wrapper, which is exactly why this is opt-in rather than a default change.

The bundled classic themes are intentionally left untouched (they ship their own searchform.php). The core/search block change stays in WordPress/gutenberg#78485.

Trac ticket: https://core.trac.wordpress.org/ticket/65288

Plan, per the ticket: ship opt-in in 7.1 with a dev note, and revisit flipping the default (e.g. for block themes first) once the ecosystem has had time to adapt.

Tests live in tests/phpunit/tests/general/getSearchForm.php (9 tests, 22 assertions) covering the default markup, the wrapped markup, aria_label placement, the xhtml fallback, the filter, and the theme-support default + override.

… forms.

Twenty Sixteen, Twenty Seventeen, Twenty Twenty, and Twenty Twenty-One
ship their own `searchform.php`, so the `wrap_in_search` argument added to
`get_search_form()` does not reach them automatically. Read the argument
in each template so a site that opts in (via the `search-element` theme
support, the `wrap_in_search` argument, or the `search_form_args` filter)
gets the `<search>` landmark in these themes too.

When wrapping, the form is enclosed in `<search>` and `role="search"` is
dropped from the `<form>` to avoid a nested landmark. In Twenty Twenty and
Twenty Twenty-One the `aria_label` moves onto `<search>`, since a named
`<form>` without the role would otherwise expose a second landmark.

The default output of all four templates is unchanged; the wrapper is only
emitted when the argument is set.

See #65288.
@adamsilverstein
Copy link
Copy Markdown
Member Author

Added a second commit so the four bundled classic themes (Twenty Sixteen, Twenty Seventeen, Twenty Twenty, Twenty Twenty-One) honor the opt-in too, since they ship their own searchform.php and wouldn't otherwise pick up wrap_in_search.

Their default output is unchanged (byte-for-byte); the <search> wrapper is only emitted when a site opts in via theme support, the argument, or the search_form_args filter. When wrapping, role="search" is dropped, and in Twenty Twenty / Twenty Twenty-One the aria_label moves onto <search> (a named <form> without the role would otherwise expose a second landmark).

Verified by rendering each template in both states. The description's scope/test sections are updated accordingly.

@adamsilverstein
Copy link
Copy Markdown
Member Author

@westonruter I reworked this PR to make the search wrapper opt in, via add_theme_support or as a parameter on get_search_form. That eliminates the breakage risk and gives us a path to adoption over time, perhaps even making it the default in the future. For now though, this would let theme builders adopt the modern approach, and maybe core can use it for a new core theme or block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants